using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Security.Cryptography;
using NWT;
using System.IO;
using System.Xml.Serialization;

namespace NWT
{
	public partial class MainWindow : Form
	{

		/// <summary>
		/// Manager to work with GPS deviace and send data to server
		/// </summary>
		protected GPSManager gpsManager = null;

		/// <summary>
		/// Configuration settings
		/// </summary>
		protected Configuration configuration = new Configuration();

		/// <summary>
		/// Last sent point time
		/// </summary>
		protected DateTime lastSentPointTime = DateTime.MinValue;

		/// <summary>
		/// Constructor
		/// </summary>
		public MainWindow()
		{
			AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(OnUnhandledExceptionEvent);

			InitializeComponent();
			gpsManager = new GPSManager();
			gpsManager.OnGPSDetectStatus += new GPSDetectStatusEventHandleDelegate(gpsManager_OnGPSDetectStatus);
			gpsManager.OnGPSPointReceived += new GPSPointReceivedDelegate(gpsManager_OnGPSPointReceived);
			gpsManager.OnErrorCallBack += new ErrorDelegate(gpsManager_OnErrorCallBack);
			gpsManager.OnNetworkStatusChanged += new NetworkStatusChangedDelegate(gpsManager_OnNetworkStatusChanged);
			gpsManager.OnStatusChanged += new EventHandler(gpsManager_OnStatusChanged);

			// attach to traffic counter
			GPSManager.NetworkTrafficCounter.AmountChanged += new EventHandler(NetworkTrafficCounter_AmountChanged);
		}

		/// <summary>
		/// Process traffic change value
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void NetworkTrafficCounter_AmountChanged(object sender, EventArgs e)
		{
			if (this.InvokeRequired)
				this.BeginInvoke(new EventHandler(OnAmountChanged), new object[] { sender, e });
			else
				OnAmountChanged(sender, e);
		}

		/// <summary>
		/// Process traffic change value
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		protected void OnAmountChanged(object sender, EventArgs e)
		{
			txtTrafficAmount.Text = GPSManager.NetworkTrafficCounter.Amount.ToString();
		}

		/// <summary>
		/// Process manager status changing
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		protected void OnManagerStatusChanged(object sender, EventArgs e)
		{
			goToolStripMenuItem.Text = gpsManager.Status == ManagerStatus.Stopped ? "Go" : "Stop";
			autoDetectToolStripMenuItem.Enabled = gpsManager.Status == ManagerStatus.Stopped;
			monitorToolStripMenuItem.Enabled = gpsManager.Status == ManagerStatus.Stopped;
			stopToolStripMenuItem.Enabled = gpsManager.Status != ManagerStatus.Stopped;
			configurationToolStripMenuItem.Enabled = gpsManager.Status == ManagerStatus.Stopped;

			// reset UI on stop
			if (gpsManager.Status == ManagerStatus.Stopped)
			{
				lblSentStatus.BackColor = Color.Black;
				lblSentStatus.ForeColor = Color.PaleGreen;

				txtLatitude.BackColor = Color.Black;
				txtLongitude.BackColor = Color.Black;
				txtSpeed.BackColor = Color.Black;

				txtLatitude.ForeColor = Color.PaleGreen;
				txtLongitude.ForeColor = Color.PaleGreen;
				txtSpeed.ForeColor = Color.PaleGreen;
			}
		}

		/// <summary>
		/// Process status changing
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void gpsManager_OnStatusChanged(object sender, EventArgs e)
		{
			if (this.InvokeRequired)
				this.BeginInvoke(new EventHandler(OnManagerStatusChanged), new object[] { sender, e });
			else
				OnManagerStatusChanged(sender, e);
		}

		/// <summary>
		/// Process device error event
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="ex"></param>
		void OnErrorCallBack(object sender, Exception ex)
		{
			string text = ex.ToString();
			SetStatusText(System.Environment.NewLine + DateTime.Now.ToString() + ": " + text);

			// write to log file
			WriteLog(ex.ToString());

			// check to start autodetect
			if (go && ex is GPSDeviceNotFound)
			{
				autoDetectedGPSDeviceParameters = null;
				gpsManager.StartDetectingGPSParameters(true);
			}
		}

		/// <summary>
		/// Exception handler
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="args"></param>
		void OnUnhandledExceptionEvent(object sender, UnhandledExceptionEventArgs args)
		{
			if (this.InvokeRequired)
				this.BeginInvoke(new ErrorDelegate(OnErrorCallBack), new object[] { sender, (Exception)args.ExceptionObject });
			else
				OnErrorCallBack(sender, (Exception)args.ExceptionObject);
		}

		/// <summary>
		/// Process device error event
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="ex"></param>
		void gpsManager_OnErrorCallBack(object sender, Exception ex)
		{
			if (this.InvokeRequired)
				this.BeginInvoke(new ErrorDelegate(OnErrorCallBack), new object[] { sender, ex });
			else
				OnErrorCallBack(sender, ex);
		}

		/// <summary>
		/// Notify of detection GPS parameters
		/// </summary>
		/// <param name="status"></param>
		/// <param name="port"></param>
		/// <param name="baudRate"></param>
		/// <param name="e"></param>
		protected void gpsManager_OnGPSDetectStatus(object sender, GPSDetectStatus status, string port, int baudRate, Exception e)
		{
			if (this.InvokeRequired)
				this.BeginInvoke(new GPSDetectStatusEventHandleDelegate(OnGPSDetectStatus), new object[] { sender, status, port, baudRate, e });
			else
				OnGPSDetectStatus(sender, status, port, baudRate, e);
		}

		/// <summary>
		/// Process notification
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="status"></param>
		/// <param name="port"></param>
		/// <param name="baudRate"></param>
		/// <param name="e"></param>
		protected void OnGPSDetectStatus(object sender, GPSDetectStatus status, string port, int baudRate, Exception e)
		{
			// process autodetect status
			string text = string.Empty;
			switch (status)
			{
				case GPSDetectStatus.Started:
					text = "Autodetect GPS device started...";
					break;
				case GPSDetectStatus.Testing:
					text = string.Format("Testing port {0} at speed {1}", port, baudRate);
					break;
				case GPSDetectStatus.Tested:
					text = string.Format("Port {0} tested", port);
					break;
				case GPSDetectStatus.Finished:
					text = "Autodetect GPS device finished";
					// start monitoring
					if (go && autoDetectedGPSDeviceParameters != null)
						StartMonitoring();
					go = false;
					break;
				case GPSDetectStatus.NotFound:
					autoDetectedGPSDeviceParameters = null;
					text = "GPS device not found";
					break;
				case GPSDetectStatus.Detected:
					text = string.Format("Detected GPS device on {0} at speed {1}", port, baudRate);
					autoDetectedGPSDeviceParameters = gpsManager.AutoDetectedGPSParameters;
					this.txtGPSPort.Text = autoDetectedGPSDeviceParameters.PortName;
					break;
				case GPSDetectStatus.Error:
					text = string.Format("Error testing port {0}: {1}", port, e.Message);
					break;
			}
			SetStatusText(text);
		}

		/// <summary>
		/// Update network status
		/// </summary>
		/// <param name="networkStatus"></param>
		protected void UpdateNetworkStatus(object sender, NetworkStatus networkStatus)
		{
			switch (networkStatus)
			{
				case NetworkStatus.Sending:
					lblSentStatus.BackColor = Color.PaleGreen;
					lblSentStatus.ForeColor = Color.Black;
					break;
				case NetworkStatus.Waiting:
					lblSentStatus.BackColor = Color.Black;
					lblSentStatus.ForeColor = Color.PaleGreen;
					break;
				case NetworkStatus.SentOK:
					lblSentStatus.BackColor = Color.Black;
					lblSentStatus.ForeColor = Color.PaleGreen;
					lastSentPointTime = DateTime.Now;
					UpdateSentStatus();
					break;
			}
		}

		/// <summary>
		/// Process network status
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="networkStatus"></param>
		void gpsManager_OnNetworkStatusChanged(object sender, NetworkStatus networkStatus)
		{
			if (this.InvokeRequired)
				this.BeginInvoke(new NetworkStatusChangedDelegate(UpdateNetworkStatus), new object[] { sender, networkStatus });
			else
				UpdateNetworkStatus(sender, networkStatus);
		}

		/// <summary>
		/// Update UI elements
		/// </summary>
		/// <param name="gpsPoint"></param>
		protected void UpdatePointPosition(object sender, GPSPoint gpsPoint)
		{
			// prevent autodetect on data arrived
			go = false;

			// write to log
			float latitude = gpsPoint.latitude ?? default(float);
			float longitude = gpsPoint.longitude ?? default(float);
			float speed = gpsPoint.speed ?? default(float);
			float altitude = gpsPoint.altitude ?? default(float);
			if (chkLog.Checked)
			{
				string text = string.Format("{4}{0}:{1}, {2}, h:{3}",
					latitude.ToString("0.#####").Replace(",", "."),
					longitude.ToString("0.#####").Replace(",", "."),
					speed.ToString("0.##").Replace(",", "."),
					altitude.ToString("0.##").Replace(",", "."),
					object.Equals(gpsPoint.fixObtained, true) ? string.Empty : "*");
				SetStatusText(text);
			}

			// set control text
			txtLatitude.Text = latitude.ToString("0.#####").Replace(",", ".");
			txtLongitude.Text = longitude.ToString("0.#####").Replace(",", ".");
			txtSpeed.Text = speed.ToString("0.##").Replace(",", ".");

			// update colors
			if (gpsPoint.fixObtained.HasValue && gpsPoint.fixObtained.Value)
			{
				txtLatitude.BackColor = Color.Black;
				txtLongitude.BackColor = Color.Black;
				txtSpeed.BackColor = Color.Black;

				txtLatitude.ForeColor = Color.PaleGreen;
				txtLongitude.ForeColor = Color.PaleGreen;
				txtSpeed.ForeColor = Color.PaleGreen;
			}
			else
			{
				txtLatitude.BackColor = Color.PaleGreen;
				txtLongitude.BackColor = Color.PaleGreen;
				txtSpeed.BackColor = Color.PaleGreen;

				txtLatitude.ForeColor = Color.Black;
				txtLongitude.ForeColor = Color.Black;
				txtSpeed.ForeColor = Color.Black;
			}
		}

		/// <summary>
		/// Process new point 
		/// </summary>
		/// <param name="gpsManager"></param>
		/// <param name="gpsPoint"></param>
		void gpsManager_OnGPSPointReceived(object sender, GPSPoint gpsPoint)
		{
			if (this.InvokeRequired)
				this.BeginInvoke(new GPSPointReceivedDelegate(UpdatePointPosition), new object[] { sender, gpsPoint });
			else
				UpdatePointPosition(sender, gpsPoint);
		}

		#region Auto Detect parameters

		/// <summary>
		/// Show status text
		/// </summary>
		/// <param name="status"></param>
		protected void SetStatusText(string status)
		{
			txtLogWindow.AppendText(status + System.Environment.NewLine);
		}

		#endregion

		/// <summary>
		/// Autoprocessing GPS params
		/// </summary>
		protected bool go = false;

		/// <summary>
		/// Autodetected GPS device parameter
		/// </summary>
		protected GPSDeviceParameters autoDetectedGPSDeviceParameters = null;

		/// <summary>
		/// Start detect GPS device
		/// </summary>
		protected void StartAutoDetect()
		{
			autoDetectedGPSDeviceParameters = null;
			gpsManager.StartDetectingGPSParameters(true);
		}

		/// <summary>
		/// Start monitoring process
		/// </summary>
		protected void StartMonitoring()
		{
			if (autoDetectedGPSDeviceParameters != null)
				configuration.DeviceParameters = autoDetectedGPSDeviceParameters;

			// update port setting
			if (this.txtGPSPort.Text != configuration.DeviceParameters.PortName)
			{
				configuration.DeviceParameters.PortName = this.txtGPSPort.Text;
				configuration.Modified = true;
			}

			// start
			gpsManager.StartProcessData(configuration);

			// Show info text
			string info = string.Empty;
			info += Environment.NewLine + "Name:\t\t" + configuration.NetworkParameters.MarkerName;
			info += Environment.NewLine + "Game:\t\t" + configuration.NetworkParameters.GameName;
			info += Environment.NewLine + "User:\t\t" + configuration.NetworkParameters.UserLogin;
			info += Environment.NewLine + "Pause:\t\t" + configuration.NetworkParameters.SendingPause + " ms";
			info += Environment.NewLine + "MaxPause:\t" + configuration.NetworkParameters.MaxSendingPause + " ms";
			info += Environment.NewLine + "Distance:\t\t" + configuration.NetworkParameters.DistanceInMeters + " m";
			info += Environment.NewLine;
			SetStatusText(info);
		}

		/// <summary>
		/// Auto find GPS and process GPS data
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void btnGo_Click(object sender, EventArgs e)
		{
			go = true;
			StartMonitoring();
		}

		/// <summary>
		/// Autodetect params of GPS device
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void btnAutodetect_Click(object sender, EventArgs e)
		{
			go = false;
			StartAutoDetect();
		}

		/// <summary>
		/// Monitor GPS data
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void btnMonitor_Click(object sender, EventArgs e)
		{
			go = false;
			StartMonitoring();
		}

		/// <summary>
		/// Stop processing and detecting
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void btnStop_Click(object sender, EventArgs e)
		{
			go = false;
			gpsManager.Stop();
		}

		/// <summary>
		/// Clear text from status window
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void btnClear_Click(object sender, EventArgs e)
		{
			txtLogWindow.Text = string.Empty;
		}

		/// <summary>
		/// Config file name
		/// </summary>
		protected string ConfigFileName
		{
			get
			{
				return Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "NWT_PC_config.config");
			}
		}

		/// <summary>
		/// Log file name
		/// </summary>
		protected string LogFileName
		{
			get
			{
				return Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "NWT_PC_log.log");
			}
		}

		/// <summary>
		/// Process form closed event
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void MainWindow_FormClosed(object sender, FormClosedEventArgs e)
		{
			try
			{
				// ask to save settings
				if (!File.Exists(this.ConfigFileName)
					|| (configuration.Modified
					&& MessageBox.Show("Save settings?", "GPS Sender", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1) == DialogResult.Yes))
				{
					configuration.SaveConfiguration(this.ConfigFileName);
				}
				// save traffic
				else if (configuration.TrafficCalculate)
				{
					if (File.Exists(this.ConfigFileName))
					{
						// load settings
						configuration.LoadConfiguration(this.ConfigFileName);
						configuration.TrafficCalculate = true;
					}
					configuration.TrafficAmount = GPSManager.NetworkTrafficCounter.Amount;
					configuration.SaveConfiguration(this.ConfigFileName);
				}

				// log to file
				WriteLog("Program finished");
			}
			catch (Exception ex)
			{
				// log error
				OnErrorCallBack(this, ex);
			}
		}

		/// <summary>
		/// Write text to log
		/// </summary>
		/// <param name="text"></param>
		protected void WriteLog(string text)
		{
			if (configuration.LogToFile)
				FileLogger.Write(this.LogFileName, text);
		}

		/// <summary>
		/// Perform form loading
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void MainWindow_Load(object sender, EventArgs e)
		{
			try
			{
				// write to log file
				WriteLog("Program started");

				// load settings
				configuration.LoadConfiguration(this.ConfigFileName);
				GPSManager.NetworkTrafficCounter.Amount = configuration.TrafficAmount;
			}
			catch (System.IO.FileNotFoundException)
			{
				// config file not found
				OnErrorCallBack(this, new ApplicationException(string.Format("Configuration file {0} not found.", this.ConfigFileName)));
			}
			catch (Exception ex)
			{
				// log error
				OnErrorCallBack(this, ex);
			}

			// compute password
			if (configuration.UseMD5ForPassword)
			{
				MD5 md5 = new MD5CryptoServiceProvider();
				byte[] password = Encoding.UTF8.GetBytes(configuration.NetworkParameters.UserPassword);
				byte[] hashedPassword = md5.ComputeHash(password);
				StringBuilder sb = new StringBuilder();
				foreach (byte b1 in hashedPassword)
					sb.Append(b1.ToString("x"));
				configuration.NetworkParameters.UserPassword = sb.ToString();
			}

			// update UI
			OnSettingsChanged();
		}

		/// <summary>
		/// Perform settings changed
		/// </summary>
		protected void OnSettingsChanged()
		{
			// set com port
			this.txtGPSPort.Text = configuration.DeviceParameters.PortName;
			gpsManager.GPSAccessType = 1;
			gpsManager.NetworkSendingMethod = configuration.NetworkSendingMethod;
			pnlTraffic.Visible = configuration.TrafficCalculate;
		}

		/// <summary>
		/// Perform item event processing
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void goToolStripMenuItem_Click(object sender, EventArgs e)
		{
			if (gpsManager.Status == ManagerStatus.Stopped)
			{
				go = true;
				StartMonitoring();
			}
			else
			{
				go = false;
				gpsManager.Stop();
			}
		}

		/// <summary>
		/// Perform item event processing
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void autoDetectToolStripMenuItem_Click(object sender, EventArgs e)
		{
			autoDetectedGPSDeviceParameters = null;
			gpsManager.StartDetectingGPSParameters(true);
		}

		/// <summary>
		/// Perform item event processing
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void monitorToolStripMenuItem_Click(object sender, EventArgs e)
		{
			go = false;
			StartMonitoring();
		}

		/// <summary>
		/// Perform item event processing
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void stopToolStripMenuItem_Click(object sender, EventArgs e)
		{
			go = false;
			gpsManager.Stop();
		}

		/// <summary>
		/// Perform item event processing
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void clearToolStripMenuItem_Click_1(object sender, EventArgs e)
		{
			txtLogWindow.Text = string.Empty;
		}

		/// <summary>
		/// Perform item event processing
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void configurationToolStripMenuItem_Click(object sender, EventArgs e)
		{
			try
			{
				SettingsWindow sw = new SettingsWindow(configuration);
				if (sw.ShowDialog() == DialogResult.OK)
				{
					configuration.Modified = true;
					// update UI
					OnSettingsChanged();
					// save settings
					configuration.SaveConfiguration(this.ConfigFileName);
					configuration.Modified = false;
				}
				sw.Dispose();
			}
			catch (Exception ex)
			{
				// log error
				OnErrorCallBack(this, ex);
			}
		}

		/// <summary>
		/// Perform item event processing
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void exitToolStripMenuItem_Click(object sender, EventArgs e)
		{
			Application.Exit();
		}

		/// <summary>
		/// Update sent status
		/// </summary>
		protected void UpdateSentStatus()
		{
			if (lastSentPointTime == DateTime.MinValue)
				lblSentStatus.Text = string.Empty;
			else
			{
				TimeSpan delta = DateTime.Now - lastSentPointTime;
				lblSentStatus.Text = string.Format("{0}{1}{2}{3}{4}{5}",
					delta.Hours > 0 ? delta.Hours.ToString() : string.Empty,
					delta.Hours > 0 ? "h " : string.Empty,
					delta.Minutes > 0 ? delta.Minutes.ToString() : string.Empty,
					delta.Minutes > 0 ? "m " : string.Empty,
					delta.Seconds.ToString(),
					"s");
			}
		}

		/// <summary>
		/// Update sent status
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void tmrSentStatus_Tick(object sender, EventArgs e)
		{
			UpdateSentStatus();
		}
	}
}
